package xyz.lwm.lazycat.web.route;

import xyz.lwm.lazycat.annotation.Order;
import xyz.lwm.lazycat.beans.Indexes;
import xyz.lwm.lazycat.utility.Assert;
import xyz.lwm.lazycat.utility.It;
import xyz.lwm.lazycat.utility.MapUtil;
import xyz.lwm.lazycat.utility.PathUtil;
import xyz.lwm.lazycat.utility.StringUtil;
import xyz.lwm.lazycat.utility.reflect.AnnotationUtil;
import xyz.lwm.lazycat.utility.reflect.Annotations;
import xyz.lwm.lazycat.web.WebException;
import xyz.lwm.lazycat.web.exception.ExceptionMapperImpl;
import xyz.lwm.lazycat.web.handle.Handler;
import xyz.lwm.lazycat.web.handle.HandlerChain;
import xyz.lwm.lazycat.web.handle.HandlerType;
import xyz.lwm.lazycat.web.settings.WebSetting;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 路由表
 *
 * @author lwm
 */
public class RouteTableImpl implements RouteTable {

    // all before routes
    private final List<Route> beforeRoutes = new ArrayList<>();

    // all after routes
    private final List<Route> afterRoutes = new ArrayList<>();

    // key: route.getKey(); value: hashMap<HandlerType, route>
    private final Map<String, Map<HandlerType, Route>> standardRoutes = new HashMap<>();

    // variable routes. key: route.getKey(); value: hashMap<HandlerType, route>
    private final Map<String, Map<HandlerType, Route>> variableRoutes = new HashMap<>();

    // resource route
    private final AtomicReference<Route> resourceRoute = new AtomicReference<>();

    // general routes
    private final List<Route> generalRoutes = new ArrayList<>();

    // exception mapper
    private final ExceptionMapperImpl exceptionMapper;

    // web properties
    private final WebSetting webProperties;

    // is initialized ?
    private boolean initialized;

    public RouteTableImpl(ExceptionMapperImpl exceptionMapper, WebSetting webProperties) {
        this.exceptionMapper = exceptionMapper;
        this.webProperties = webProperties;
    }

    @Override
    public void add(Route route) {
        generalRoutes.add(route);
    }

    @Override
    public void add(String path, HandlerType type, Handler handler) {
        Assert.isTrue(!initialized, "RouteTable has already been initialized.");

        switch (type) {
            case BEFORE:
                beforeRoutes.add(new SimpleRoute(path, type, handler));
                break;
            case AFTER:
                afterRoutes.add(new SimpleRoute(path, type, handler));
                break;
            case REDIRECT:
                addAction(path, HandlerType.GET, handler);
                break;
            case STATIC:
                resourceRoute.set(new SimpleRoute(path, type, handler));
                break;
            case PROXY:
                if (webProperties.getProxy().isEnable()) {
                    generalRoutes.add(new SimpleRoute(path, type, handler));
                }
                break;
            case GET:
            case PUT:
            case POST:
            case PATCH:
            case DELETE:
                addAction(path, type, handler);
                break;
        }
    }

    private void addAction(String path, HandlerType type, Handler handler) {
        if (VariableRoute.isVariational(path)) {
            VariableRoute route = new VariableRoute(path, type, handler);
            Map<HandlerType, Route> routeMap = variableRoutes.computeIfAbsent(route.getKey(), k -> new HashMap<>());
            Route existed = routeMap.get(route.getType());
            if (existed != null) {
                throw new WebException(StringUtil.format("Route[{}, {}] cannot be added, because Route[{}, {}] exists",
                        route.getType(), route.getPath(), existed.getType(), existed.getPath()));
            }
            routeMap.put(route.getType(), route);
        } else {
            StandardRoute route = new StandardRoute(path, type, handler);
            Map<HandlerType, Route> routeMap = standardRoutes.computeIfAbsent(route.getKey(), k -> new HashMap<>());
            Assert.isTrue(!routeMap.containsKey(route.getType()), "Route[{}, {}] exists.", route.getType(), route.getPath());
            routeMap.put(route.getType(), route);
        }
    }

    /**
     * 前置路由、后置路由、通用路由排序; 前置路由和后置路由分类,并聚合到标准路由和动态路由中. <br>
     * 前后置路由不会聚合到通用路由中, 这是两个体系, 通用路由要自行实现
     */
    @Override
    public void classify() {
        Assert.isTrue(!initialized, "RouteTable has already been initialized.");
        // 前置后置路由(拦截器)内部排序
        sort(beforeRoutes);
        sort(afterRoutes);
        // 通用路由内部排序, 通过排序可以将自定义的路由放在最前面
        sort(generalRoutes);
        // 标准路由聚合前置后置拦截器
        standardRoutes.forEach((k, v) ->
                v.values().forEach(this::setHandlerChain));
        // 动态路由聚合前置后置拦截器
        variableRoutes.forEach((k, v) ->
                v.values().forEach(this::setHandlerChain));
        initialized = true;
    }

    /**
     * 内部排序, 通过 {@link Handler} 的 {@link Order} 注解进行排序
     *
     * @param routes 路由列表
     */
    private void sort(List<Route> routes) {
        routes.sort(Comparator.comparing(route -> {
            Annotations annotations = AnnotationUtil.getAnnotations(route.getHandler().getClass());
            return It.of(annotations.getAnnotation(Order.class)).let(Order::value).orElse(Indexes.DEFAULT).get();
        }));
    }

    /**
     * 筛选 before after handlers 并组装到 HandlerChain 中
     *
     * @param route 路由
     */
    private void setHandlerChain(Route route) {
        if (route instanceof StandardRoute || route instanceof VariableRoute) {
            List<Handler> handlers = new ArrayList<>();
            beforeRoutes.stream().filter(r -> r.matches(route.getPath()))
                    .forEach(r -> handlers.add(r.getHandler()));
            handlers.add(route.getHandler());
            afterRoutes.stream().filter(r -> r.matches(route.getPath()))
                    .forEach(r -> handlers.add(r.getHandler()));
            ((AbstractRoute) route).setHandler(new HandlerChain(handlers, exceptionMapper));
        }
    }

    @Override
    public Route get(HandlerType type, String path) {
        Assert.isTrue(initialized, "RouteTable has not been initialized.");

        String key = PathUtil.normalizePath(path);
        Map<HandlerType, Route> routeMap = standardRoutes.get(key);
        if (MapUtil.isNotEmpty(routeMap)) {
            // path 存在, 可能 type 不准确, 返回可能为 null
            return routeMap.get(type);
        }
        // path 不存在, 再从动态路由中查找, 动态路由使用正则匹配
        for (String reg : variableRoutes.keySet()) {
            if (key.matches(reg)) {
                // path 匹配成功, 可能 type 不准确, 返回可能为 null
                return variableRoutes.get(reg).get(type);
            }
        }
        // 通用 route 匹配
        for (Route r : generalRoutes) {
            // 返回最先匹配到的代理路由
            // 如果有优先级, 按 order 选择, 值越小越先匹配
            if (r.matches(key)) {
                return r;
            }
        }
        // 静态资源路由
        return resourceRoute.get();
    }

}
